﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Z2.Core
{

	/// <summary>
	/// Particle engine
	/// </summary>
	public class ParticleEngine
	{

		#region Const

		private const int MaximumParticles = 150;

		#endregion

		#region Members

		private List<ParticleBasic> m_particles;
		
		private static volatile ParticleEngine _instance;
		private static volatile object _syncRoot = new object();
		private static volatile object _particleLock = new object();
		private readonly Dictionary<ParticleType, string> m_assetCache;

		#endregion

		#region Constructor

		private ParticleEngine()
		{
			Debug.WriteLine("Creating particle engine");
			this.m_particles = new List<ParticleBasic>();
			this.m_assetCache = new Dictionary<ParticleType, string>();

			Debug.WriteLine("Initializing asset cache");
			this.m_assetCache.Add(ParticleType.StarYellow, "star");
			this.m_assetCache.Add(ParticleType.StarRed, "star_red");
			this.m_assetCache.Add(ParticleType.Water, "waterSplash");
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets instance of <see cref="ParticleEngine"/>
		/// </summary>
		public static ParticleEngine Instance
		{
			get 
			{
				if (_instance == null)
				{
					lock (_syncRoot)
					{
						if (_instance == null)
							_instance = new ParticleEngine();
					}
				}
				return _instance;
			}
		}

		/// <summary>
		/// Gets number of active particles
		/// </summary>
		public int Count
		{
			get { return m_particles.Count; }
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Creates one particle at defined position
		/// </summary>
		/// <param name="where">Position</param>
		public void Create(Vector2 where)
		{
			this.Create(where, ParticleSettings.Default);
		}

		/// <summary>
		/// Creates items at defined position
		/// </summary>
		/// <param name="where">Position</param>
		/// <param name="power">Power</param>
		/// <param name="ammount">Number of items</param>
		/// <param name="type">Item type</param>
		public void CreateItems(Vector2 where, ItemType type, ParticleSettings settings)
		{
			for (int i = 0; i < settings.Ammount; ++i)
			{
				ParticleBasic particle = new ParticleItem(this.GetItem(type));
				particle.Move = new Vector2(
					RandomGenerator.Random.Next((int)-settings.Power.X, (int)settings.Power.X),
					-RandomGenerator.Random.Next(0, (int)settings.Power.Y));

				this.AddParticle(particle, where);
			}
		}

		public void Create(Vector2 where, ParticleSettings settings)
		{
			for (int i = 0; i < settings.Ammount; ++i)
			{
				ParticleBasic particle = new ParticleBasic(this.m_assetCache[settings.Type]);
				particle.IgnoresObjects = settings.Type == ParticleType.Water;
				particle.LiveToMin = (int)settings.LifeTime.X;
				particle.LiveToMax = (int)settings.LifeTime.Y;
				particle.HasLight = settings.HasLight;
				particle.Move = new Vector2(
					RandomGenerator.Random.Next((int)-settings.Power.X, (int)settings.Power.X),
					-RandomGenerator.Random.Next(0, (int)settings.Power.Y));

				this.AddParticle(particle, where);
			}
		}

		public void CreateStatic(Vector2 where, ParticleSettings settings)
		{
			for (int i = 0; i < settings.Ammount; ++i)
			{
				ParticleStatic particle = new ParticleStatic(this.m_assetCache[settings.Type]);
				particle.Move = new Vector2(
					RandomGenerator.Random.Next((int)-settings.Power.X, (int)settings.Power.X),
					-RandomGenerator.Random.Next(0, (int)settings.Power.Y));

				this.AddParticle(particle, where);
			}
		}

		/// <summary>
		/// Creates slices from object defined in <paramref name="obj"/>
		/// </summary>
		/// <param name="obj">Object to slice to</param>
		/// <param name="power">Power</param>
		public void Slice(ObjectBase obj, Vector2 power)
		{
			if(obj.Sprite.Slices == null) return;
			int half = obj.Sprite.SlicesHorizontal / 2;
			int sign = 1;

			for(int i = 0; i < obj.Sprite.Slices.Count; ++i)
			{
				Sprite sprite = obj.Sprite.Slices[i];
				ParticleBasic particle = new ParticleBasic(sprite);
				int y = i / obj.Sprite.SlicesHorizontal;
				int x = i - (y * obj.Sprite.SlicesHorizontal);
				sign = (x + 1) > half ? 1 : -1;
				
				particle.Move = new Vector2
				(
					sign * RandomGenerator.Random.Next(1, (int)power.X),
					power.Y
				);

				AddParticle(particle, new Vector2
				(
					obj.Position.X + (x * sprite.Texture.Width),
					obj.Position.Y + (y * sprite.Texture.Height)
				));
			}
			
			obj.Destroy();
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Adds new particle to the list
		/// </summary>
		/// <param name="particle"></param>
		/// <param name="position"></param>
		private void AddParticle(ParticleBasic particle, Vector2 position)
		{
			// reg. event on delete
			particle.Delete += this.Particle_Delete;
			particle.Position = new Vector2(position.X, position.Y);
			particle.Initialize(); // initialize particle

			// add to the list
			this.m_particles.Add(particle);
			
			// check for maximum
			if (this.m_particles.Count > ParticleEngine.MaximumParticles)
			{
				this.m_particles[0].Destroy();
				this.m_particles[0].Dispose();
				this.m_particles.RemoveAt(0);
			}
		}

		/// <summary>
		/// Creates new item from <see cref="ItemType"/>
		/// </summary>
		/// <param name="itemType"><see cref="ItemType"/></param>
		/// <returns><see cref="ItemBase"/></returns>
		private ItemBase GetItem(ItemType itemType)
		{
			switch (itemType)
			{
				case ItemType.Strawberry:
					return new Strawberry();
				case ItemType.Apple:
					return new Apple();
			}

			throw new NotSupportedException();
		}

		/// <summary>
		/// Draws all particles
		/// </summary>
		/// <param name="batch"></param>
		/// <param name="camera"></param>
		internal void Draw(SpriteBatch batch, Vector2 camera)
		{
			for (int i = this.m_particles.Count - 1; i > -1; --i)
			{
				this.m_particles[i].Draw(batch);
			}
		}

		internal void DrawLight(SpriteBatch batch)
		{
			for (int i = this.m_particles.Count - 1; i > -1; --i)
			{
				this.m_particles[i].DrawLight(batch);
			}
		}

		/// <summary>
		/// Updates particles
		/// </summary>
		internal void Update()
		{
			for (int i = this.m_particles.Count - 1; i > -1; --i)
			{
				this.m_particles[i].Update();
			}
		}

		#endregion

		#region Event Handlers

		private void Particle_Delete(object sender, EventArgs e)
		{
			ParticleBasic particle = (ParticleBasic)sender;
			particle.Delete -= this.Particle_Delete;
			this.m_particles.Remove(particle);
		}

		#endregion

		#region Override Methods
		
		public override string ToString()
		{
			return string.Format("Total: {0}", m_particles.Count);
		}

		#endregion

	}
}
